package com.bestcem.xm.qdes.service.impl;

import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.component.mybatis.service.impl.XmServiceImpl;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.qdes.controller.param.QuestionRestNoParam;
import com.bestcem.xm.qdes.entity.QdesLibProject;
import com.bestcem.xm.qdes.entity.QdesProject;
import com.bestcem.xm.qdes.entity.QdesQstruct;
import com.bestcem.xm.qdes.entity.QdesQuestion;
import com.bestcem.xm.qdes.enums.ProjectPtypeEnum;
import com.bestcem.xm.qdes.enums.QuestionCustomTypeEnum;
import com.bestcem.xm.qdes.enums.QuestionTypeEnum;
import com.bestcem.xm.qdes.mapper.QdesLibProjectMapper;
import com.bestcem.xm.qdes.mapper.QdesProjectMapper;
import com.bestcem.xm.qdes.mapper.QdesQstructMapper;
import com.bestcem.xm.qdes.mapper.QdesQuestionMapper;
import com.bestcem.xm.qdes.mq.dto.StatusDto;
import com.bestcem.xm.qdes.mq.send.biz.QdesMessageSendService;
import com.bestcem.xm.qdes.service.QdesQuestionService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.bestcem.xm.qdes.grpc.v1.services.QStructStatus.QSTRUCT_STATUS_DRAFT;
import static com.bestcem.xm.qdes.util.QstructConstant.*;

@Slf4j
@RequiredArgsConstructor
@Service
public class QdesQuestionServiceImpl extends XmServiceImpl<QdesQuestionMapper, QdesQuestion> implements QdesQuestionService {

    private @NonNull QdesQstructMapper qdesQstructMapper;
    private @NonNull QdesLibProjectMapper qdesLibprojectMapper;
    private @NonNull QdesProjectMapper qdesProjectMapper;

    private @NonNull RedisService redisService;
    private @NonNull QdesMessageSendService qdesMessageSendService;

    private static final String DOU = ",";

    private static final String QSTRUCT_SECRET = "_qstruct_secret_";

    private static final Integer SCORE_RULE_ALL_RIGHT = 1;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addOneQuestion(String structId, JSONObject param) {
        Integer gid = param.getInteger(GID);
        param.put(SEQ, gid);
        // 题目时长计算规则：https://wiki.idiaoyan.cn/pages/viewpage.action?pageId=10422398
        int costTime = calculateTimecost(param);
        param.put(DEFAULT_Q_TIMECOST, costTime);
        QdesQuestion question = new QdesQuestion();
        question.setStructId(structId);
        question.setGid(gid);
        question.setData(JSONUtil.toJsonStr(param));
        Boolean modified = param.containsKey(MODIFIED) ? param.getBoolean(MODIFIED) : Boolean.FALSE;
        question.setModified(modified);
        if (param.containsKey(EXT_ID)) {
            question.setExtId(param.getString(EXT_ID));
        }
        baseMapper.insert(question);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delOneQuestion(String structId, String gid) {
        return deleteQuestion(structId, gid);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delQuestions(String structId, JSONObject param) {
        int count = 0;
        JSONArray array = param.getJSONArray(GID_LIST);
        for (int i = 0; i < array.size(); i++) {
            String gid = array.getString(i);
            count += deleteQuestion(structId, gid);
        }
        return count;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int resetQuestionNo(QdesQstruct qstruct, QuestionRestNoParam param) {
        String qstructId = param.getStructId();
        List<Integer> gids = param.getGids();
        int m = 0;
        for (Integer gid : gids) {
            Map<String, Object> map = new HashMap<>();
            map.put("cid", "Q".concat(String.valueOf(m + 1)));
            map.put("structId", qstructId);
            map.put("gid", gid);
            //UPDATE ds_qdes_question
            //SET data = json_replace(data, '$.cid', 'Q11')
            //WHERE struct_id='6310747158e5432294263c6d' and gid=2;
            baseMapper.updateCidByParam(map);
            m++;
        }

        LambdaUpdateWrapper<QdesQstruct> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper
                .set(QdesQstruct::getStatus, QSTRUCT_STATUS_DRAFT.getNumber())
                .eq(QdesQstruct::getId, qstruct.getId());
        qdesQstructMapper.update(null, updateWrapper);

        return m;
    }

    @Override
    public void checkCid(JSONObject param) {
        if (!param.containsKey(CID)) {
            throw new BusinessException("cid是必要参数");
        }
        if (!ReUtil.isMatch(CID_FORMAT_REGEX, param.getString(CID))) {
            throw new BusinessException("题目编号cid必须以字母开头，且只能包含字母、数字和英文下划线");
        }
        if (param.getString(CID).contains(D_UNDERSCORE)) {
            throw new BusinessException("题目编号cid不允许输入双英文下划线");
        }
    }

    @Override
    public void checkOid(JSONObject param) {
        JSONArray list = param.containsKey(ITEMS) ? param.getJSONArray(ITEMS) : new JSONArray();
        for (int i = 0; i < list.size(); i++) {
            JSONObject it = list.getJSONObject(i);
            if (it.containsKey(OID) && StrUtil.isNotEmpty(it.getString(OID))) {
                if (!ReUtil.isMatch(PatternPool.NUMBERS, it.getString(OID))) {
                    throw new BusinessException("选项编号oid不是数字");
                }
            }
        }
    }

    @Override
    public QdesQstruct checkAndGetQstruct(String id) {
        QdesQstruct struct = qdesQstructMapper.selectById(id);
        return Optional.ofNullable(struct).orElseThrow(() -> new BusinessException(String.format("qstruct[id=%s] not found", id)));
    }

    @Override
    public QdesProject checkAndGetProject(QdesQstruct struct) {
        QdesProject project = qdesProjectMapper.selectById(struct.getProjectId());
        if (ObjectUtils.isEmpty(project)) {
            throw new BusinessException(String.format("qdes:project[id=%s] not found", struct.getProjectId()));
        }

        if (StrUtil.isNotBlank(project.getSource())) {
            throw new BusinessException("extend project, update is forbidden");
        }
        if (project.getVersion().intValue() != struct.getVersion().intValue()) {
            throw new BusinessException(String.format("qstruct[id=%s] is stale", struct.getId()));
        }

        boolean b = PROJ_EDITABLE_STATUS_LIST.stream().noneMatch(a -> a.equals(project.getStatus()));
        if (b) {
            throw new BusinessException(String.format("project.status is %s, update is forbidden", project.getStatus()));
        }
        return project;
    }

    @Override
    public QdesQuestion checkAndGetQuestion(String structId, String gid) {
        LambdaQueryWrapper<QdesQuestion> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(QdesQuestion::getStructId, structId);
        wrapper.eq(QdesQuestion::getGid, gid);
        //wrapper.eq(QdesQuestion::getDeleted, 0);
        //wrapper.last("limit 1");
        QdesQuestion question = baseMapper.selectOne(wrapper);
        return Optional.ofNullable(question).orElseThrow(() -> new BusinessException(String.format("question[structID=%s,gid=%s] not found", structId, gid)));
    }

    @Override
    public void checkOptionsGroupEffect(JSONObject data, QdesQstruct struct, String structId, String gid) {
        //开启选项分组
        if (data.containsKey(OPTIONS_GROUP_EFFECT) && data.getBoolean(OPTIONS_GROUP_EFFECT)) {
            Set<String> keys = new HashSet<>();
            AtomicBoolean a = new AtomicBoolean(false);
            // 选项关联存在但是值为空，relatedRefs：{"6":[],"9":[]}
            // 示例relatedRefs={"6":[{"sourceGid":2,"targetGid":6,"type":"O","mode":"select"}],"9":[{"sourceGid":6,"targetGid":10,"type":"O","mode":"select"}]}
            JSONObject qstructdata = JSONObject.parseObject(struct.getData());
            JSONObject refobj = qstructdata.getJSONObject(RELATED_REFS);
            if (!ObjectUtils.isEmpty(refobj) && !refobj.isEmpty()) {
                keys = refobj.keySet();
                refobj.forEach((key, value) -> {
                    if (JSONUtil.isTypeJSONArray(JSON.toJSONString(value))) {
                        JSONArray jsonArray = (JSONArray) value;
                        if (!CollectionUtils.isEmpty(jsonArray)) {
                            a.set(true);
                        }
                    }
                });
            }

            if (a.get() && keys.contains(gid)) {
                throw new BusinessException(String.format("question[structID=%s,gid=%s] option association already exists", structId, gid));
            }
        }
    }

    @Override
    public void checkOptionOrigin(JSONObject data, QdesQuestion question, String structId, String gid) {
        //开启选项关联
        if (data.containsKey(ITEMS) && !CollectionUtils.isEmpty(data.getJSONArray(ITEMS))) {
            JSONObject questiondata = JSONObject.parseObject(question.getData());
            List<JSONObject> items = JSONArray.parseArray(data.getJSONArray(ITEMS).toJSONString(), JSONObject.class);
            boolean b = items.stream().anyMatch(it1 -> it1.containsKey(OPTION_ORIGIN) && questiondata.containsKey(OPTIONS_GROUP_EFFECT) && questiondata.getBoolean(OPTIONS_GROUP_EFFECT));

            if (b) {
                throw new BusinessException(String.format("question[structID=%s,gid=%s] grouping already exists", structId, gid));
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateQuestion(String structId, String gid, JSONObject data, QdesQuestion question, QdesQstruct struct) {
        modifyOneQuestion(gid, data, question, struct);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateQuestions(String structId, List<String> gids, List<JSONObject> datas, List<QdesQuestion> questions, QdesQstruct struct, int count) {
        for (int i = 0; i < count; i++) {
            modifyOneQuestion(gids.get(i), datas.get(i), questions.get(i), struct);
        }
    }

    @Override
    //@Transactional(rollbackFor = Exception.class)
    public void updateQstruct(QdesQstruct struct, QdesProject project, JSONObject param) {
        JSONObject scoreconfig = JSONObject.parseObject(struct.getScoreConfig());
        HashMap<String, Object> multiplequestions = new LinkedHashMap<>();
        JSONArray results = param.getJSONObject(DATA).getJSONArray(ITEMS);
        for (int i = 0; i < results.size(); i++) {
            JSONObject result = results.getJSONObject(i);
            JSONArray items = result.getJSONArray(ITEMS);
            for (int j = 0; j < items.size(); j++) {
                JSONObject item = items.getJSONObject(j);
                if (item.containsKey(GID)) {
                    String gid = StrUtil.isNotBlank(item.getString(GID)) ? item.getString(GID) : "-1";
                    multiplequestions.put(gid, scoreconfig.getOrDefault(gid, SCORE_RULE_ALL_RIGHT));
                }
            }
        }
        JSONObject multiplequestionsjsonobject = new JSONObject();
        multiplequestionsjsonobject.putAll(multiplequestions);
        param.put("score_config", multiplequestionsjsonobject);
        param.put("status", QSTRUCT_STATUS_DRAFT.getNumber());

        //await model_utils.update_qstruct(session, struct_id, params)
        modifyQstructEntity(struct.getId(), param);
        //await qdes_dao.project_dao.update_by_id(session, proj.id)  # 更新问卷时间
        QdesProject up = new QdesProject();
        up.setId(project.getId());
        qdesProjectMapper.updateById(up);

        LambdaQueryWrapper<QdesQuestion> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(QdesQuestion::getStructId, struct.getId());
        List<QdesQuestion> questionList = baseMapper.selectList(wrapper);
        Map<Integer, JSONObject> allqs = new HashMap<>();
        questionList.stream().filter(q -> !q.getDeleted()).forEach(question -> allqs.put(question.getGid(), JSONObject.parseObject(question.getData())));
        Optional.ofNullable(param.getJSONObject("data"))
                .map(it -> it.getJSONArray("items"))
                .filter(ob -> ob.size() == 1)
                .ifPresent(qsitems -> {
                    //JSONArray qsitems = param.getJSONObject("data").getJSONArray("items");
                    //if (!ObjectUtils.isEmpty(qsitems) && !qsitems.isEmpty() && qsitems.size() == 1) {
                    JSONObject it = qsitems.getJSONObject(0);
                    JSONArray ar = it.getJSONArray("items");
                    if (!ObjectUtils.isEmpty(ar) && !ar.isEmpty()) {
                        JSONObject yt = ar.getJSONObject(0);
                        if (yt.keySet().size() > 2) {
                            JSONArray questions = new JSONArray();
                            //questions = get_qstruct_questions(params['data'], all_qids)
                            // 符合条件的question.data集合
                            getQstructQuestions(param.getJSONObject("data"), allqs, questions);

                            Map<String, String> map = new HashMap<>();
                            map.put("projectId", struct.getProjectId());
                            map.put("version", struct.getVersion().toString());
                            map.put("secretKey", struct.getSecretKey());
                            questions.forEach(sob -> {
                                JSONObject dd = (JSONObject) sob;
                                genQuestionSign(map, dd.getString("gid"), dd);

                                LambdaUpdateWrapper<QdesQuestion> updateWrapper = new LambdaUpdateWrapper<>();
                                updateWrapper
                                        .set(QdesQuestion::getData, JSON.toJSONString(dd))
                                        .eq(QdesQuestion::getStructId, struct.getId())
                                        .eq(QdesQuestion::getGid, dd.getString("gid"));
                                baseMapper.update(null, updateWrapper);
                            });
                        }
                    }

                    //}
                });

        StatusDto statusDto = new StatusDto().setProjectId(project.getId());
        qdesMessageSendService.projectUpdatedSend(statusDto);

        //判断是否为模板问卷
        if (ProjectPtypeEnum.TEMPLATE.getValue() == project.getPtype()) {
            // 更新LibProject中题目数量question_count
            LambdaUpdateWrapper<QdesLibProject> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper
                    .eq(QdesLibProject::getProjectId, project.getId())
                    .set(QdesLibProject::getQuestionCount, allqs.size());
            qdesLibprojectMapper.update(null, updateWrapper);
        }
        //删除问卷缓存
        //redisService.del("survey_get_project#" + struct.getProjectId());
    }

    private void modifyQstructEntity(String id, JSONObject param) {
        QdesQstruct qstruct = new QdesQstruct();
        qstruct.setId(id);
        if (param.containsKey("projectID")) {
            throw new BusinessException("update qstruct.projectID is not allowed");
        }
        if (param.containsKey("version")) {
            throw new BusinessException("pdate qstruct.version is not allowed");
        }
        if (param.containsKey("releaseDT")) {
            throw new BusinessException("update qstruct.releaseDT is not allowed");
        }
        if (param.containsKey("data")) {
            qstruct.setData(Optional.ofNullable(param.getJSONObject("data")).orElse(new JSONObject()).toJSONString());
        }
        if (param.containsKey("withdrawDT")) {
            throw new BusinessException("update qstruct.withdrawDT is not allowed");
        }
        if (param.containsKey("templates")) {
            throw new BusinessException("update qstruct.templates is not allowed");
        }
        if (param.containsKey("attachments")) {
            throw new BusinessException("update qstruct.attachments is not allowed");
        }
        if (param.containsKey("csses")) {
            throw new BusinessException("update qstruct.csses is not allowed");
        }
        if (param.containsKey("gcss")) {
            throw new BusinessException("update qstruct.gcss is not allowed");
        }
        if (param.containsKey("ucss")) {
            throw new BusinessException("update qstruct.ucss is not allowed");
        }
        if (param.containsKey("scores")) {
            throw new BusinessException("update qstruct.scores is not allowed");
        }
        if (param.containsKey("_secret")) {
            throw new BusinessException("update qstruct._secret is not allowed");
        }
        if (param.containsKey("policy")) {
            throw new BusinessException("update qstruct.policy is not allowed");
        }

        if (param.containsKey("status")) {
            qstruct.setStatus(param.getInteger("status"));
        }
        if (param.containsKey("score_config")) {
            qstruct.setScoreConfig(Optional.ofNullable(param.getJSONObject("score_config")).orElse(new JSONObject()).toJSONString());
        }
        //is_copy_demo 不在表字段，设了也是摆设
        //if (param.containsKey("is_copy_demo")) {
        //
        //}
        if (param.containsKey("gjs")) {
            qstruct.setGjs((Optional.ofNullable(param.getJSONObject("gjs")).orElse(new JSONObject()).toJSONString()));
        }

        qdesQstructMapper.updateById(qstruct);
    }

    public void modifyOneQuestion(String gid, JSONObject data, QdesQuestion question, QdesQstruct struct) {
        Map<String, String> map = getStructMap(struct);
        genQuestionSign(map, gid, data);

        // 题目时长计算规则：https://wiki.idiaoyan.cn/pages/viewpage.action?pageId=10422398
        int costTime = calculateTimecost(data);
        data.put(DEFAULT_Q_TIMECOST, costTime);

        //处理自定义时长q_timecost
        JSONObject questiondata = JSONObject.parseObject(question.getData());
        //旧的自定义时长存在，需要重新设置自定义时长 且 (新数据里没有自定义时长 或者 新的自定义时长与旧的不等)
        if (questiondata.containsKey(Q_TIMECOST)) {
            int oldqtimecost = questiondata.getIntValue(Q_TIMECOST);
            if (!data.containsKey(Q_TIMECOST) || data.getIntValue(Q_TIMECOST) != oldqtimecost) {
                data.put(Q_TIMECOST, oldqtimecost);
            }
        }

        log.info("modifyOneQuestion data:{}", JSONUtil.toJsonStr(data));
        // sig更新到数据库
        QdesQuestion upq = new QdesQuestion();
        upq.setData(JSONUtil.toJsonStr(data));
        upq.setModified(Boolean.TRUE);
        upq.setId(question.getId());
        baseMapper.updateById(upq);

        StatusDto statusDto = new StatusDto().setProjectId(struct.getProjectId());
        qdesMessageSendService.projectUpdatedSend(statusDto);
        //处理分值设置updata_qs_scores
        updateScoresOfStruct(gid, data, struct);
    }

    public void updateScoresOfStruct(String gid, JSONObject data, QdesQstruct struct) {
        JSONObject scores = JSONObject.parseObject(struct.getScores());
        if (!ObjectUtils.isEmpty(scores) && !scores.isEmpty() && scores.containsKey(gid)) {
            JSONArray oldScores = scores.getJSONArray(gid);
            JSONArray newscores = new JSONArray();

            if (!CollectionUtils.isEmpty(oldScores) && (data.getString(QTYPE).equals(QuestionTypeEnum.QUESTION_TYPE_SINGLE.getValue()) || data.getString(QTYPE).equals(QuestionTypeEnum.QUESTION_TYPE_MULTIPLE.getValue()))) {
                // "6": [{"o_gid": "7", "score": "1"}, {"o_gid": "8", "score": "3"}],
                JSONArray itarry = data.getJSONArray(ITEMS);
                List<Integer> newogids = new ArrayList<>();
                itarry.forEach(ot1 -> {
                    JSONObject it1 = (JSONObject) ot1;
                    newogids.add(it1.getInteger(GID));
                });

                oldScores.forEach(ot1 -> {
                    JSONObject it1 = (JSONObject) ot1;
                    Integer l = it1.getInteger(O_Gid);
                    if (newogids.contains(l)) {
                        newscores.add(it1);
                    }
                });
            }

            if (!CollectionUtils.isEmpty(oldScores) && (data.getString(QTYPE).equals(QuestionTypeEnum.QUESTION_TYPE_MATRIX_SINGLE.getValue()) || data.getString(QTYPE).equals(QuestionTypeEnum.QUESTION_TYPE_MATRIX_MULTIPLE.getValue()))) {
                // "14": [{"o_gid": "15", "r_gid": "17", "score": "1"}, {"o_gid": "15", "r_gid": "18", "score": "1"}]

                JSONArray itarry1 = data.getJSONArray(ROWS_ITEMS);
                List<Integer> newrgids = new ArrayList<>();
                itarry1.forEach(ot1 -> {
                    JSONObject it1 = (JSONObject) ot1;
                    newrgids.add(it1.getInteger(GID));
                });

                JSONArray itarry = data.getJSONArray(ITEMS);
                List<Integer> newogids = new ArrayList<>();
                for (int i = 0; i < itarry.size(); i++) {
                    JSONObject it1 = itarry.getJSONObject(i);
                    newogids.add(it1.getInteger(GID));
                }

                oldScores.forEach(ot1 -> {
                    JSONObject it1 = (JSONObject) ot1;
                    Integer l = it1.getInteger(O_Gid);
                    Integer j = it1.getInteger(R_Gid);
                    if (newogids.contains(l) && newrgids.contains(j)) {
                        newscores.add(it1);
                    }
                });
            }

            if (!CollectionUtils.isEmpty(newscores)) {
                scores.put(gid, newscores);
                log.info("updateScoresOfStruct gid:{} newscores:{}", gid, JSONUtil.toJsonStr(newscores));
                QdesQstruct qstruct = new QdesQstruct();
                qstruct.setId(struct.getId());
                qstruct.setScores(JSONUtil.toJsonStr(scores));
                qdesQstructMapper.updateById(qstruct);
            }
        }
    }


    //public void genQuestionSign(Map<String, String> map, String gid, JSONObject data) {
    //    //"attachData":{"openAttach":true,"allowNotUpload":false,"onlyImage":true,"showThumbnail":true,"minNum":1,"maxNum":1,"minSize":0,"maxSize":50,"openOAttach":true,"allowONotUpload":false,"onlyOImage":true,"showOThumbnail":true,"minONum":1,"maxONum":1,"minOSize":0,"maxOSize":50,"excludeOptionList":[],"allowSameName":true}
    //    JSONObject attachData = data.getJSONObject(ATTACH_DATA);
    //    if (!ObjectUtils.isEmpty(attachData) && attachData.containsKey(OPEN_ATTACH) && attachData.getBoolean(OPEN_ATTACH)) {
    //        HashMap<String, Object> signMap = new HashMap<String, Object>() {{
    //            put("pid", map.get("projectId"));
    //            put("version", map.get("version"));
    //            put("qid", gid);
    //            //后面几个没从接口参数里面取？
    //            put("openAttach", false);
    //            put("onlyImage", false);
    //            put("minNum", "");
    //            put("maxNum", "");
    //            put("minSize", "");
    //            put("maxSize", "");
    //        }};
    //        attachData.put(SIG, SignUtil.genSign(signMap, map.get("secretKey")));
    //    }
    //}

    /**
     * 从数据库取值，同步到redis
     *
     * @param qstruct
     * @return java.util.Map<java.lang.String, java.lang.String>
     * @author chenglong.yue <chenglong.yue@idiaoyan.com>
     * @date 2022/9/20 16:16
     */
    public Map<String, String> getStructMap(QdesQstruct qstruct) {
        String structId = qstruct.getId();
        // _qstruct_secret_63214e263e2c0c000878b3d8
        String key = QSTRUCT_SECRET.concat(structId);
        String str = redisService.getString(key);
        String s = Optional.ofNullable(str).orElseGet(() -> {
            String ret = qstruct.getProjectId().concat(DOU).concat(qstruct.getVersion().toString()).concat(DOU).concat(qstruct.getSecretKey());
            redisService.set(key, ret, 24, TimeUnit.HOURS);
            return ret;
        });
        List<String> list = Arrays.asList(s.split(DOU));
        Map<String, String> map = new HashMap<>();
        map.put("projectId", list.get(0));
        map.put("version", list.get(1));
        map.put("secretKey", list.get(2));
        return map;
    }

    public int deleteQuestion(String structId, String gid) {
        QdesQuestion question = this.checkAndGetQuestion(structId, gid);
        QdesQstruct qstruct = this.checkAndGetQstruct(structId);
        if (!ObjectUtils.isEmpty(qstruct)) {
            JSONObject data = JSONObject.parseObject(question.getData());
            if (StrUtil.isNotBlank(qstruct.getTemplates()) && JSONUtil.isTypeJSONArray(qstruct.getTemplates())) {
                String cid = data.getString(CID);
                JSONArray templete = JSON.parseArray(qstruct.getTemplates());
                if (!templete.isEmpty()) {
                    log.info("delOneQuestion templete start:{}", qstruct.getTemplates());
                    templete.stream().filter(ot1 -> {
                        JSONObject it1 = (JSONObject) ot1;
                        JSONArray array1 = it1.getJSONArray(Q_LIST);
                        return !array1.isEmpty();
                    }).forEach(ot2 -> {
                        JSONObject it2 = (JSONObject) ot2;
                        JSONArray array2 = it2.getJSONArray(Q_LIST);
                        array2.remove(cid);
                    });
                    log.info("delOneQuestion templete end:{}", JSONUtil.toJsonStr(templete));

                    LambdaUpdateWrapper<QdesQstruct> updateWrapper = new LambdaUpdateWrapper<>();
                    updateWrapper
                            .set(QdesQstruct::getTemplates, JSONUtil.toJsonStr(templete))
                            .eq(QdesQstruct::getId, structId);
                    qdesQstructMapper.update(null, updateWrapper);
                }
            }
        }

        JSONObject mqdata = new JSONObject();
        mqdata.put("project_id", qstruct.getProjectId());
        mqdata.put("version", qstruct.getVersion());
        JSONArray tmp = new JSONArray();
        JSONObject oj = new JSONObject();
        oj.put("type", "delete");
        oj.put("gid", gid);
        tmp.add(oj);
        mqdata.put("data", tmp);
        qdesMessageSendService.projectQuestionsChangedSend(mqdata);
        return baseMapper.deleteById(question.getId());
    }

    /**
     * 根据题型 计算最小答题时长
     *
     * @param data
     * @return int
     * @author chenglong.yue <chenglong.yue@idiaoyan.com>
     * @date 2022/9/13 12:00
     */
    public int calculateTimecost(JSONObject data) {
        // 默认2秒
        BigDecimal costTime;
        BigDecimal minTime = BigDecimal.valueOf(2d);
        String qtype = data.containsKey(QTYPE) ? data.getString(QTYPE) : StrUtil.EMPTY;
        String cqtype = data.containsKey(CUSTOM_QTYPE) ? data.getString(CUSTOM_QTYPE) : StrUtil.EMPTY;
        String title = data.containsKey(TITLE) ? data.getString(TITLE) : StrUtil.EMPTY;

        String qtitle = HtmlUtil.cleanHtmlTag(title);
        JSONArray options = data.containsKey(ITEMS) ? data.getJSONArray(ITEMS) : new JSONArray();
        JSONArray rows = data.containsKey(ROWS_ITEMS) ? data.getJSONArray(ROWS_ITEMS) : new JSONArray();

        //题目浏览时间（题目标题去除富文本标签后的文字）1秒10字
        costTime = BigDecimal.valueOf(NumberUtil.div(qtitle.length(), 10));

        if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_DESCRIPTOR.getValue())) {
            //描述题 基础时长默认1秒
            minTime = BigDecimal.valueOf(1d);
        }

        if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_CITY.getValue())) {
            int infoLevel = data.containsKey(INFO_LEVEL) ? data.getIntValue(INFO_LEVEL) : 1;
            costTime = NumberUtil.add(costTime, infoLevel);
        }

        // 单选题、多选题;图片选择题
        if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_SINGLE.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_MULTIPLE.getValue())) {
            if (cqtype.equals(QuestionCustomTypeEnum.QUESTION_CUSTOM_TYPE_IMAGE_SELECT.getValue())) {
                // 图片选择题
                costTime = NumberUtil.add(costTime, NumberUtil.mul(options.size(), 0.2));
            } else {
                // 单选题、多选题
                costTime = NumberUtil.add(costTime, NumberUtil.mul(options.size(), 0.1));
            }
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_CASCADE.getValue())) {
            // 级联题 "otype": "cascader_option_2"
            options = data.containsKey(OPTION_LIST) ? data.getJSONArray(OPTION_LIST) : new JSONArray();
            JSONObject last = options.getJSONObject(options.size() - 1);
            List<String> list = Arrays.asList(last.getString(OTYPE).split(T_OPTION_T));
            Collections.reverse(list);
            int tmplevel = Integer.parseInt(list.stream().findFirst().orElse("1"));
            int cascaderLevel = Math.max(1, tmplevel);
            costTime = NumberUtil.add(costTime, cascaderLevel);
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_MATRIX_SINGLE.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_MATRIX_MULTIPLE.getValue())) {// 矩阵单选题、矩阵多选题
            if (cqtype.equals(QuestionCustomTypeEnum.QUESTION_CUSTOM_TYPE_MATRIX_MULTIPLE_CLASSIFY.getValue())) {
                // 分类题
                costTime = NumberUtil.add(costTime, NumberUtil.mul(rows.size(), 0.1), NumberUtil.mul(options.size(), 2));
            } else {
                costTime = NumberUtil.add(costTime, NumberUtil.mul(rows.size(), options.size(), 0.2));
            }
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_BLANK.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_MULTIPLE_BLANK.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_AUTO_TABLE.getValue())) {// 矩阵单选题、矩阵多选题
            if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_BLANK.getValue())) {
                int op = !options.isEmpty() ? options.size() : 1;
                costTime = NumberUtil.add(costTime, NumberUtil.mul(op, 1.5));
            } else {
                costTime = NumberUtil.add(costTime, NumberUtil.mul(options.size(), 1.5));
            }
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_MATRIX_BLANK.getValue())) {
            //矩阵填空题
            costTime = NumberUtil.add(costTime, NumberUtil.mul(rows.size(), options.size(), 1.5));
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_SCORE.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_PROPORTION.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_SORT.getValue())) {
            //打分题、NPS题、游标打分题，比重题，排序题
            costTime = NumberUtil.add(costTime, NumberUtil.mul(options.size(), 2));
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_EVALUATION.getValue())) {
            //评价题
            costTime = NumberUtil.add(costTime, NumberUtil.mul(options.size(), 2), 1.5);
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_MATRIX_SCORE.getValue())) {
            //矩阵打分题
            costTime = NumberUtil.add(costTime, NumberUtil.mul(rows.size(), options.size(), 0.6));
        } else if (qtype.equals(QuestionTypeEnum.QUESTION_TYPE_TIMEDELTA.getValue())
                || qtype.equals(QuestionTypeEnum.QUESTION_TYPE_TIMESTAMP.getValue())) {
            // 时间、日期
            costTime = NumberUtil.add(costTime, 2);
        }

        // 不能低于2s
        if (costTime.compareTo(minTime) < 0) {
            costTime = minTime;
        }
        // 四舍五入取整
        return NumberUtil.round(costTime, 0).intValue();
    }

    @Override
    public List<QdesQuestion> getQuestionList(String structID, Collection<?> gids) {
        LambdaQueryWrapper<QdesQuestion> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QdesQuestion::getStructId, structID);
        if (ObjectUtil.isNotEmpty(gids)) {
            wrapper.in(QdesQuestion::getGid, gids);
        }
        return baseMapper.selectList(wrapper);
    }
}
